package cn.virens.common.easyexcel;

import cn.hutool.core.util.PageUtil;
import cn.idev.excel.EasyExcel;
import cn.idev.excel.ExcelWriter;
import cn.idev.excel.converters.Converter;
import cn.idev.excel.write.handler.WriteHandler;
import cn.idev.excel.write.metadata.WriteSheet;
import cn.virens.common.easyexcel.dict.ExcelDict;
import cn.virens.common.easyexcel.dict.ExcelDictStack;
import cn.virens.common.mybatis.entity.param.PageParam;
import cn.virens.common.mybatis.entity.result.PageResult;
import cn.virens.common.util.core.Assert;
import cn.virens.common.util.core.stream.StreamUtil;
import cn.virens.common.util.exception.APIException;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;
import org.dromara.hutool.core.math.NumberUtil;
import org.dromara.hutool.core.net.url.UrlEncoder;
import org.dromara.hutool.core.util.ObjUtil;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;

public class ExcelExportExecutorBase<P extends PageParam> {
    /**
     * 数据查询接口
     */
    protected Function<P, PageResult<?>> selectFun;
    /**
     * 导出进度回调接口
     */
    protected Consumer<Integer> progressFun;
    /**
     * 导出完成回调接口
     */
    protected Consumer<Integer> completeFun;

    protected List<WriteHandler> writeHandlers;
    protected List<Converter<?>> converters;

    protected final ExcelDictStack dictStack;
    protected final P pageParam;

    protected final Integer pageSize; // 每页条数

    public ExcelExportExecutorBase(P pageParam) {
        this(128, pageParam);
    }

    public ExcelExportExecutorBase(Integer pageSize, P pageParam) {
        this.dictStack = ExcelDictStack.registerDictStack();
        this.pageParam = Assert.isNull(pageParam);
        this.pageSize = Assert.isNull(pageSize);
        this.pageParam.setPageSize(pageSize);
        this.pageParam.setCount(true);
        this.pageParam.setPageNum(1);
    }

    /**
     * 设置数据查询接口
     */
    public void setProivde(Function<P, PageResult<?>> selectFun) {
        this.selectFun = Assert.isNull(selectFun);
    }

    /**
     * 设置数据查询接口
     */
    public void setSelectFun(Function<P, PageResult<?>> selectFun) {
        this.selectFun = selectFun;
    }

    /**
     * 设置导出进度回调接口
     */
    public void setProgressFun(Consumer<Integer> progressFun) {
        this.progressFun = progressFun;
    }

    /**
     * 设置导出完成回调接口
     */
    public void setCompleteFun(Consumer<Integer> completeFun) {
        this.completeFun = completeFun;
    }

    /**
     * 添加转换字典
     */
    public void putDict(ExcelDict dict) throws APIException {
        this.dictStack.putDict(dict);
    }

    /**
     * 添加 查询参数
     */
    public void setParams(Map<String, Object> params) {
        this.pageParam.setParams(params);
    }

    /**
     * 设置排序方式
     */
    public void setOrderBy(String orderBy) {
        this.pageParam.setOrderBy(orderBy);
    }

    /**
     * 添加查询参数
     */
    public void addParam(String key, Object value) {
        this.pageParam.addParam(key, value);
    }

    protected List<WriteHandler> getWriteHandlers() throws APIException {
        if (writeHandlers != null && writeHandlers.isEmpty()) {
            List<WriteHandler> answer = new ArrayList<>();
            answer.addAll(dictStack.getWriteHandlerList());
            answer.addAll(writeHandlers);

            return answer;
        }

        return writeHandlers;
    }

    protected List<Converter<?>> getConverters() throws APIException {
        if (converters != null && converters.isEmpty()) {
            List<Converter<?>> answer = new ArrayList<>();
            answer.addAll(dictStack.getConverterList());
            answer.addAll(converters);

            return answer;
        }

        return converters;
    }

    /**
     * 添加额外的写入处理
     */
    public void addWriteHandlers(WriteHandler writeHandler) {
        if (this.writeHandlers == null) {
            this.writeHandlers = new ArrayList<>();
        }

        this.writeHandlers.add(writeHandler);
    }

    /**
     * 添加额外的数据转换处理
     */
    public void addConverters(Converter<?> converter) {
        if (this.converters == null) {
            this.converters = new ArrayList<>();
        }

        this.converters.add(converter);
    }

    /**
     * 导出对应的模板表
     */
    public void template(HttpServletResponse response, String name, Class<?> clazz) throws IOException {
        this.template(response, EasyExcel.writerSheet(0, name).head(clazz).build());
    }

    /**
     * 导出对应的模板表
     */
    public void template(OutputStream out, String name, Class<?> clazz) throws IOException {
        this.template(out, EasyExcel.writerSheet(0, name).head(clazz).build());
    }

    /**
     * 导出对应的模板表
     */
    public void template(HttpServletResponse response, WriteSheet sheet) throws IOException {
        response.setHeader("Content-Disposition", "attachment;filename=" + fname(sheet));
        response.setHeader("Content-Type", "application/octet-stream");

        try (ServletOutputStream out = response.getOutputStream()) {
            this.template(out, sheet);
        }
    }

    /**
     * 导出对应的模板表
     */
    public void template(OutputStream out, WriteSheet sheet) throws IOException {
        sheet.setCustomWriteHandlerList(getWriteHandlers());
        sheet.setCustomConverterList(getConverters());
        sheet.setAutomaticMergeHead(true);
        sheet.setAutoTrim(true);

        try (ExcelWriter w = EasyExcel.write(out).build()) {
            w.write(new ArrayList<>(), sheet);
        }
    }

    /**
     * 将数据导出到表格(分页查询数据)
     */
    public void export(HttpServletResponse response, String name, Class<?> clazz) throws IOException {
        this.export(response, EasyExcel.writerSheet(0, name).head(clazz).build());
    }

    /**
     * 将数据导出到表格(分页查询数据)
     */
    public void export(OutputStream out, String name, Class<?> clazz) throws IOException {
        this.export(out, EasyExcel.writerSheet(0, name).head(clazz).build());
    }

    /**
     * 将数据导出到表格(分页查询数据)
     */
    public void export0(HttpServletResponse response, String name) throws IOException {
        this.export(response, EasyExcel.writerSheet(0, name).build());
    }

    /**
     * 将数据导出到表格(分页查询数据)
     */
    public void export0(OutputStream out, String name) throws IOException {
        this.export(out, EasyExcel.writerSheet(0, name).build());
    }

    /**
     * 将数据导出到表格(分页查询数据)
     */
    public void export(HttpServletResponse response, WriteSheet writeSheet) throws IOException {
        response.setHeader("Content-Disposition", "attachment;filename=" + fname(writeSheet));
        response.setHeader("Content-Type", "application/octet-stream");

        try (ServletOutputStream out = response.getOutputStream()) {
            this.export(out, writeSheet);
        }
    }

    /**
     * 将数据导出到表格(分页查询数据)
     */
    public void export(OutputStream out, WriteSheet writeSheet) throws IOException {
        try (ExcelWriter w = EasyExcel.write(out).build()) {
            this.exportSheet(w, writeSheet);
        } finally {
            this.doCompleteFun(1);
        }
    }

    /**
     * 将数据导出到表格(分页查询数据)
     */
    public void exportSheet(ExcelWriter writer, WriteSheet writeSheet) throws IOException {
        writeSheet.setCustomWriteHandlerList(getWriteHandlers());
        writeSheet.setCustomConverterList(getConverters());
        writeSheet.setAutomaticMergeHead(true);
        writeSheet.setAutoTrim(true);

        // 查询第一页数据: 获取总数据量----------------------------------------------
        PageResult<?> pageResult = selectFun.apply(pageParam);
        if (pageResult != null && pageResult.getTotal() > 0) {
            writer.write(pageResult.getList(), writeSheet);

            doProgressFun(1);
        }

        // 如果数据量不需要进行分页，就直接结束后续处理--------------------------------
        if (pageResult == null || pageResult.getTotal() <= pageSize) {
            return;
        }

        // 根据总数据量进行分页查询并写入表格-----------------------------------------
        var pageCount = NumberUtil.count((int) pageResult.getTotal(), pageSize);
        for (int pageNumber = 1; pageNumber < pageCount; pageNumber++) {
            var pageResultUser = selectFun.apply(cloneParam(pageNumber));

            writer.write(pageResultUser.getList(), writeSheet);

            doProgressFun(pageNumber);
        }
    }

    protected String fname(WriteSheet sheet) throws APIException {
        return UrlEncoder.encodeAll(sheet.getSheetName()) + ".xlsx";
    }

    /**
     * 克隆查询条件对象
     *
     * @param pageNum 查询页码
     */
    protected P cloneParam(Integer pageNum) throws APIException {
        P pageParamUser = ObjUtil.clone(pageParam);
        pageParamUser.setPageNum(pageNum + 1);
        pageParamUser.setPageSize(pageSize);
        pageParamUser.setCount(false);

        return pageParamUser;
    }

    protected void doCompleteFun(Integer arg) throws APIException {
        if (completeFun != null && arg != null) {
            completeFun.accept(arg);
        }
    }

    protected void doProgressFun(Integer arg) throws APIException {
        if (progressFun != null && arg != null) {
            progressFun.accept(arg);
        }
    }
}
